import java.util.ArrayList;

/**
 * IDE: Eclipse
 *
 * @author Justin Niles
 * @author Grant Foster
 * @author Bryce Langlotz
 * @author Max Honosky
 *
 */
public class CavityCalculator {
	boolean verbose = false;
	AtomArray atomArray;
	float resolution;

	/**
	 * Three dimensional representation of the "filling" grid. Each box can
	 */
	int[][][] resGrid;
	int xGridLength;
	int yGridLength;
	int zGridLength;

	/**
	 * Scaling represents the number of "steps" that must be taken inside the
	 * resGrid to equal 1 step in euclidian space.
	 *
	 * E.g. If the scaling is equal to 4, then to move one unit in the
	 * x-direction, 4 units must be moved in the x-direction of the resGrid.
	 */
	float scaling;

	static float xMin;
	static float yMin;
	static float zMin;

	public CavityCalculator(AtomArray aa, float res, float probeRadius) {
		atomArray = aa;
		resolution = res;
		scaling = 1 / res;

		// First setup resGrid
		xMin = atomArray.getXMin();
		yMin = atomArray.getYMin();
		zMin = atomArray.getZMin();
		float xLength = atomArray.getXMax() - xMin;
		float yLength = atomArray.getYMax() - yMin;
		float zLength = atomArray.getZMax() - zMin;
		xGridLength = (int) Math.ceil(xLength * scaling) + 1;
		yGridLength = (int) Math.ceil(yLength * scaling) + 1;
		zGridLength = (int) Math.ceil(zLength * scaling) + 1;
		resGrid = new int[xGridLength][yGridLength][zGridLength];

		// Now offset all atoms so that the farthest atom in the negative
		// directions is at (0,0,0).
		for (Atom a : atomArray) {
			a.xPos += Math.abs(xMin);
			a.yPos += Math.abs(yMin);
			a.zPos += Math.abs(zMin);
			a.radius += probeRadius;
		}
		if (verbose) {
			System.out.println("Adjusted Bounds:");
			atomArray.printBounds();
		}
	}

	/**
	 * Skeleton method to be called that calls the other
	 * calculateAtomIntersections method for each z plane. This method
	 */
	public void calculateAtomIntersections() {
		for (int zLocInArray = 0; zLocInArray < zGridLength; zLocInArray++) {
			float calculatedZ = zLocInArray * resolution;
			if (verbose) {
				System.out.println("Filling in z-plane " + calculatedZ);
			}
			calculateAtomIntersections(calculatedZ);
		}
	}

	public void calculateAtomIntersections(float zLoc) {
		/*
		 * circleCS (circle Cross Sections) Formatting: (0) circle x location
		 * (1) circle y location (2) circle radius (...repeat...)
		 */
		ArrayList<Float> circleCS = new ArrayList<Float>();

		/*
		 * First calculate all circles that are created from atoms intersecting
		 * with the given z plane and store them in circleCS
		 */
		if (verbose) {
			System.out
					.println("    Calculating circle cross sections on zplane...");
		}
		for (Atom a : atomArray) {
			float circleRadius = (float) Math
					.sqrt((Math.pow(a.radius, 2) - Math.pow((zLoc - a.zPos), 2)));
			// circleRadius >= 0 means that the atom is touching
			// the given z plane
			if (circleRadius >= 0) {
				circleCS.add(a.xPos);
				circleCS.add(a.yPos);
				circleCS.add(circleRadius);
			}
		}
		if (verbose) {
			System.out.println("    DONE! Number of intersecting circles: "
					+ circleCS.size() / 3);

			/*
			 * Next fill all cubes in the resGrid that are touching or within
			 * any found circles
			 */
			System.out.println("    Now filling in intersecting cubes...");
		}
		for (int xLoc = 0; xLoc < xGridLength; xLoc++) {
			float calculatedX = xLoc * resolution;

			for (int yLoc = 0; yLoc < yGridLength; yLoc++) {
				float calculatedY = yLoc * resolution;

				for (int i = 0; i < circleCS.size(); i = i + 3) {
					// distance between current box and current circle's center
					double distance = Math.sqrt(Math.pow(
							calculatedX - circleCS.get(i), 2)
							+ Math.pow(calculatedY - circleCS.get(i + 1), 2));
					if (distance <= circleCS.get(i + 2)) {
						resGrid[xLoc][yLoc][(int) (zLoc * scaling)] = 1;
						i = circleCS.size(); // no need to keep searching
					}
				}
			}
		}

		// Now count all occurrences in the resGrid that are equal to 1 (i.e.
		// filled)
		int count = 0;
		for (int z = 0; z < zGridLength; z++) {
			for (int y = 0; y < yGridLength; y++) {
				for (int x = 0; x < xGridLength; x++) {
					if (resGrid[x][y][z] == 1) {
						count++;
					}
				}
			}
		}
		if (verbose) {
			System.out.println("    DONE! Number of filled cubes: " + count
					+ "/" + xGridLength * yGridLength * zGridLength);
		}
	}

	public void printResGrid() {
		for (int z = 0; z < zGridLength; z++) {
			System.out.println("Z-Plane: " + z * resolution);
			for (int y = 0; y < yGridLength; y++) {
				for (int x = 0; x < xGridLength; x++) {
					System.out.print(resGrid[x][y][z]);
				}
				System.out.println("");
			}
		}
	}
}
